package cn.b.sky.flowable.graph;

import org.flowable.bpmn.model.*;
import org.flowable.engine.common.impl.Page;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.impl.HistoricActivityInstanceQueryImpl;
import org.flowable.engine.impl.HistoricActivityInstanceQueryProperty;
import org.flowable.engine.impl.cmd.GetDeploymentProcessDefinitionCmd;
import org.flowable.engine.impl.context.Context;
import org.flowable.engine.impl.persistence.entity.HistoricActivityInstanceEntityManager;
import org.flowable.engine.impl.persistence.entity.HistoricProcessInstanceEntityManager;
import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.engine.repository.ProcessDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * 根据历史，生成实时运行阶段的子图.
 */
public class ActivitiHistoryGraphBuilder {
  /**
   * log.
   */
  private static final Logger log= LoggerFactory.getLogger(ActivitiHistoryGraphBuilder.class);

  public static final HistoricActivityInstanceQueryProperty HISTORIC_ACTIVITY_INSTANCE_ID = new HistoricActivityInstanceQueryProperty(
      "ID_");
  
  /*public static final HistoricActivityInstanceQueryProperty HISTORIC_ACTIVITY_INSTANCE_ID = new HistoricActivityInstanceQueryProperty(
      "cast(ID_ as unsigned int)");*/
  
  private final String processInstanceId;

  private ProcessDefinition processDefinition;

  private List<HistoricActivityInstance> historicActivityInstances;

  private final List<HistoricActivityInstance> visitedHistoricActivityInstances = new ArrayList<HistoricActivityInstance>();

  private final Map<String, Node> nodeMap = new HashMap<String, Node>();

  public ActivitiHistoryGraphBuilder(String processInstanceId) {
    this.processInstanceId = processInstanceId;
  }

  public Graph build() {
    this.fetchProcessDefinitionEntity();
    this.fetchHistoricActivityInstances();

    Graph graph = new Graph();

    for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
      Node currentNode = new Node();
      currentNode.setId(historicActivityInstance.getId());
      currentNode.setName(historicActivityInstance.getActivityId());
      currentNode.setType(historicActivityInstance.getActivityType());
      currentNode.setActive(historicActivityInstance.getEndTime() == null);
      log.debug("currentNode : {}:{}", currentNode.getName(), currentNode.getId());

      Edge previousEdge = this.findPreviousEdge(currentNode, historicActivityInstance.getStartTime().getTime());

      if (previousEdge == null) {
        if (graph.getInitial() != null) {
          throw new IllegalStateException("already set an initial.");
        }

        graph.setInitial(currentNode);
      } else {
        log.debug("previousEdge : {}", previousEdge.getName());
      }

      nodeMap.put(currentNode.getId(), currentNode);
      visitedHistoricActivityInstances.add(historicActivityInstance);
    }

    if (graph.getInitial() == null) {
      throw new IllegalStateException("cannot find initial.");
    }

    return graph;
  }

  /**
   * 根据流程实例id获取对应的流程定义.
   */
  public void fetchProcessDefinitionEntity() {
    String processDefinitionId = Context.getCommandContext().getSession(HistoricProcessInstanceEntityManager.class)
        .findById(processInstanceId).getProcessDefinitionId();
    GetDeploymentProcessDefinitionCmd cmd = new GetDeploymentProcessDefinitionCmd(processDefinitionId);
    processDefinition = cmd.execute(Context.getCommandContext());
  }

  public void fetchHistoricActivityInstances() {
    HistoricActivityInstanceQueryImpl historicActivityInstanceQueryImpl = new HistoricActivityInstanceQueryImpl();
    // historicActivityInstanceQueryImpl.processInstanceId(processInstanceId)
    // .orderByHistoricActivityInstanceStartTime().asc();
    // : 如果用了uuid会造成这样排序出问题
    // 但是如果用startTime，可能出现因为处理速度太快，时间一样，导致次序颠倒的问题
    historicActivityInstanceQueryImpl.processInstanceId(processInstanceId).orderBy(HISTORIC_ACTIVITY_INSTANCE_ID)
            .asc().listPage(0,100);

    historicActivityInstances = Context.getCommandContext().getSession(HistoricActivityInstanceEntityManager.class)
        .findHistoricActivityInstancesByQueryCriteria(historicActivityInstanceQueryImpl);
  }

  /**
   * 找到这个节点前面的连线.
   */
  public Edge findPreviousEdge(Node currentNode, long currentStartTime) {
    String activityId = currentNode.getName();
    BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(processDefinition.getId());
    FlowNode node = (FlowNode) bpmnModel.getMainProcess().getFlowElementMap().get(activityId);
    HistoricActivityInstance nestestHistoricActivityInstance = null;
    String temporaryPvmTransitionId = null;

    // 遍历进入当前节点的所有连线
    for (SequenceFlow sequenceFlow : node.getIncomingFlows()) {
      FlowElement source = sequenceFlow.getSourceFlowElement();
      String previousActivityId = source.getId();
      HistoricActivityInstance visitiedHistoryActivityInstance = this
          .findVisitedHistoricActivityInstance(previousActivityId);

      if (visitiedHistoryActivityInstance == null) {
        continue;
      }

      // 如果上一个节点还未完成，说明不可能是从这个节点过来的，跳过
      if (visitiedHistoryActivityInstance.getEndTime() == null) {
        continue;
      }

      log.debug("current activity start time : {}", new Date(currentStartTime));
      log.debug("nestest activity end time : {}", visitiedHistoryActivityInstance.getEndTime());

      // 如果当前节点的开始时间，比上一个节点的结束时间要早，跳过
      if (currentStartTime < visitiedHistoryActivityInstance.getEndTime().getTime()) {
        continue;
      }

      if (nestestHistoricActivityInstance == null) {
        nestestHistoricActivityInstance = visitiedHistoryActivityInstance;
        temporaryPvmTransitionId = sequenceFlow.getId();
      } else if ((currentStartTime - nestestHistoricActivityInstance.getEndTime().getTime()) > (currentStartTime - visitiedHistoryActivityInstance
          .getEndTime().getTime())) {
        // 寻找离当前节点最近的上一个节点
        // 比较上一个节点的endTime与当前节点startTime的差
        nestestHistoricActivityInstance = visitiedHistoryActivityInstance;
        temporaryPvmTransitionId = sequenceFlow.getId();
      }
    }

    // 没找到上一个节点，就返回null
    if (nestestHistoricActivityInstance == null) {
      return null;
    }

    Node previousNode = nodeMap.get(nestestHistoricActivityInstance.getId());

    if (previousNode == null) {
      return null;
    }

    log.debug("previousNode : {}:{}", previousNode.getName(), previousNode.getId());

    Edge edge = new Edge();
    edge.setName(temporaryPvmTransitionId);
    previousNode.getOutgoingEdges().add(edge);
    edge.setSrc(previousNode);
    edge.setDest(currentNode);

    return edge;
  }

  public HistoricActivityInstance findVisitedHistoricActivityInstance(String activityId) {
    for (int i = visitedHistoricActivityInstances.size() - 1; i >= 0; i--) {
      HistoricActivityInstance historicActivityInstance = visitedHistoricActivityInstances.get(i);

      if (activityId.equals(historicActivityInstance.getActivityId())) {
        return historicActivityInstance;
      }
    }

    return null;
  }
}
